#!/usr/bin/env python
# encoding: utf-8


"""
@file: xianxingguihua.py
@time: 2016/11/22 下午3:19
"""
# 所有线性规划的问题的入口
from mathsolver.functions.root.jiefangchenzu import *
from mathsolver.functions.budengshi import common_opers as co
from sympy import expand, symbols, degree_list
from mathsolver.functions.budengshi.linear_pro import (XianXingGuiHua as FirstXianXingGuiHua, LinearProgramming,
                                                       LinearProgrammingDistance, XianXingGuiHhua001,
                                                       OptimalSolution001, OptimalSolution, OptimalSolutionPoint,
                                                       SlopeOptimalSolution)
from mathsolver.functions.budengshi.xianxingguihua.hancanleixing import HanCanXianXingGuiHua
from mathsolver.functions.budengshi.ineq_constants import INEQ_SEARCH_KEY


# 线性规划008
# 约束条件f(x,y)≥0，g(x,y)≥0..目标函数z=ax+by(a>0,b>0) 的最大值为m.或者最小值为n,求h(a,b)的最大值或最小值
# 例如: 若实数x,y满足不等式组\\left\\{\\begin{array}{c}3x-y≤6\\\\x-y≥-2\\\\x≥0\\\\y≥0\\end{array}\\right.,
# 目标函数z=ax+by(a>0,b>0)的最大值为12,则\\frac{2}{a}+\\frac{3}{b}的最小值为().
#  Input1: paramer1: BaseIneqs;   paramer2:BasePoly[目标表达式];
#          paramer3: 最大值(最小值) paramer4:最值;
#          paramer5:要求的表达式;   paramer6:求最大值还是最小值
class XianXingGuiHua008(BaseFunction):
    def solver(self, *args):
        self.label.add('线性规划约束条件')
        ineqs = args[0]  # 不等式组
        f1_symbol, f1_expr = args[1].sympify()  # 表达式
        op_text1, op_func1 = ('最大值', 'max') if args[2].find('最大') >= 0 else ('最小值', 'min')  # 最值文本
        op_value = args[3].sympify()  # 最值
        intert_points = co.satisfied_ineqs_intersections(ineqs)
        params = list(f1_expr.free_symbols)
        param_intls = dict(map(lambda _: (_, self.search(_)), params))
        f1_expr_subs = map(lambda _: (_[0], f1_expr.subs(_[1])), intert_points)  # 直线所过交点

        def _op_expr(_expr_subs, _op_func):
            _op_point, _opv = _expr_subs[0]
            for _point, _e in _expr_subs[1:]:
                if _op_func == 'max':
                    _op = '<'
                elif _op_func == 'min':
                    _op = '>'
                if co.is_always_hold(str(_opv) + _op + str(_e), param_intls):
                    _opv = _e
                    _op_point = _point
            return _op_point, _opv

        op_point, op_expr = _op_expr(f1_expr_subs, op_func1)
        self.steps.append(['不等式表示的平面区域如果所示阴影部分,', ''])
        line1 = BaseEq([op_point[0][0], op_point[0][2]])
        line2 = BaseEq([op_point[1][0], op_point[1][2]])

        self.steps.append(
            ['当直线' + args[1].printing(), '过直线' + line1.printing() + '与直线' + line2.printing() + '交点时取得最大值'])
        self.steps.append(['目标函数', args[1].printing() + '取得' + op_text1 + new_latex(op_value)])
        _eq = BaseEq([op_expr, op_value])
        s1, s2 = sorted(op_expr.free_symbols, key=str)
        s2_value = co.isolve_eq2(_eq, s2)[0]
        _ineqs = []
        for _expr, _intl in param_intls.items():
            if _intl:
                tmp_ineqs = co.interval_2_ineq_list(_intl, _expr)
                _ineqs.extend(tmp_ineqs)
        _ineqs = map(lambda _: [_[0].subs(s2, s2_value), _[1], _[2].subs(s2, s2_value)], _ineqs)
        from mathsolver.functions.budengshi.jiebudengshi import JieBuDengShi
        s1_intl = JieBuDengShi().solver(BaseIneqs(_ineqs)).output[0].sympify().values()[0]
        f2 = args[4].sympify()  # 最终表达式
        f2_sub = expand(f2.subs(s2, s2_value))
        op_text2, op_func2 = ('最大值', 'max') if args[5].find('最大') >= 0 else ('最小值', 'min')
        self.steps.append(['即' + _eq.printing(), ''])
        self.steps.append(['所以:' + new_latex(f2), '=' + new_latex(f2_sub)])
        last_f = BaseFunc({"var": s1, "name": "y", "type": "", "expression": f2_sub})
        if op_func2 == 'max':
            from mathsolver.functions.hanshu.zuizhi_new import MaxValue
            f2_op = MaxValue(verbose=True).solver(last_f, s1_intl)
        else:
            from mathsolver.functions.hanshu.zuizhi_new import MinValue
            f2_op = MinValue(verbose=True).solver(last_f, s1_intl)
        f2_op_value = f2_op.output[0].sympify()[0]
        self.steps.append(['故' + op_text2, '为' + new_latex(f2_op_value)])
        self.output.append(BaseNumber(f2_op_value))
        return self


# 针对一些常规线性问题无法解决的
# Input paramer1: 不等式/等式; paramer2:函数表达式; paramer3:最值文本(最大值/最小值)
class FindOptimizeValueWithMathe(BaseFunction):
    def solver(self, *args):
        self.label.add('线性规划特殊类型')
        from mathsolver.functions.budengshi.qiuzuizhi import FindOptimizeValue
        f_expr = None
        text = None
        for arg in args:
            if isinstance(arg, BaseIneqs):
                self.known[INEQ_SEARCH_KEY] = arg.sympify()
            elif isinstance(arg, BasePoly) or isinstance(arg, BaseEq):
                if isinstance(arg, BaseEq):
                    _, f_expr = arg.sympify()
                elif isinstance(arg, BasePoly):
                    f_expr = arg.sympify()
            elif isinstance(arg, str):
                text = arg
        _solver = FindOptimizeValue(known=self.known).solver(BasePoly(f_expr), text)
        self.steps.extend(_solver.steps)
        self.output.extend(_solver.output)
        return self


# 线性规划 最优解不唯一
# style1 Input paramer1: 不等式组; paramer2:目标函数(z = a*x + b*y); paramer3最值文本(最大值/最小值)
class ZuiYouJieBuWeiYiXianXingGuiHua(BaseFunction):
    def solver(self, *args):
        self.label.add('线性规划最优解不唯一')
        arg1, arg2, arg3 = args
        ineqs = co.simp_ineqs(arg1)  # 规范化不等式组
        z_symbol, f_expr = arg2.sympify()  # 目标函数
        op_text = arg3  # 最值文本
        if op_text.find('最大值') >= 0:
            op_func = max
        elif op_text.find('最小值') >= 0:
            op_func = min
        else:
            raise Exception('Illegal parameter')
        ineqs_2_eqs = map(lambda _: [_[0], _[2]], ineqs.sympify())  # step1 不等式转换为等式
        symbs = map(sympify, co.eqs_symbs(list(co.ichain(ineqs_2_eqs, [[f_expr, 0]]))))
        _x, _y = map(sympify, symbols('x, y'))
        param = list(set(symbs) - {_x, _y})[0]
        intersects = co.satisfied_ineqs_intersections(ineqs)  # step2 求区域边界的交点
        line_slopes = map(co.line_slope, ineqs_2_eqs)
        f_slope = co.line_slope(BaseEq([z_symbol, f_expr]))
        param_values = []
        for line_slope in line_slopes:  # step3 求各边界的斜率
            mo, de = co.complete_fraction(f_slope)
            if line_slope == 0 and de.has(param):  # 如果未知参数在y上面，且斜率为0，则没有对应的参数的值
                continue
            if not line_slope and mo.has(param):  # 如果未知参数在x上面，且直线没有斜率, 则没有对应的k的值
                continue
            elif not line_slope and de.has(param):  # 如果未知参数在y上面，且直线没有斜率，则相应的b等于0
                _param_values = co.isolve_eq2(BaseEq([de, '0']))
                param_values.extend(_param_values)
            else:
                _param_values = co.isolve_eq2(BaseEq([f_slope, line_slope]))
                param_values.extend(_param_values)
        verify_param_values = []
        for param_value in param_values:
            tmp_f = f_expr.subs(param, param_value)
            f_slope = co.line_slope(BaseEq([tmp_f, '0']))
            op_point = op_func(intersects, key=lambda _: tmp_f.subs(_[1]))
            l1, l2 = map(lambda _: [_[0], _[2]], op_point[0])
            l1_slope = co.line_slope(BaseEq(l1))
            l2_slope = co.line_slope(BaseEq(l2))
            if f_slope in (l1_slope, l2_slope):
                verify_param_values.append(param_value)
        if len(verify_param_values) == 1:
            self.steps.append(['求得' + new_latex(param) + '值为:', new_latex(verify_param_values[0])])
        else:
            result_print = '求得' + new_latex(param) + '值为:'
            for index, pv in enumerate(verify_param_values):
                if index == 0:
                    result_print += new_latex(pv)
                else:
                    result_print += '或' + new_latex(pv)
        self.output.append(BaseSymbolValue({param: verify_param_values}))
        return self


# 使用 mathm求最值
# Input paramer1:不等式组; paramer2:表达式; paramer3:文本
class MatheOptimizeValue(BaseFunction):
    """
    已知实数x,y满足\\left\\{\\begin{array}{c}(x-y)(x+y-2)≥0\\\\1≤x≤4\\end{array}\\right.,则x+2y的取值范围为()
    """
    def solver(self, *args):
        self.label.add('线性规划特殊类型')
        arg1, arg2, arg3 = args
        arg2 = list(co.flatten([arg2.sympify()]))
        assign_f = None
        if len(arg2) == 1:
            f_expr = arg2[0]
        elif len(arg2) == 2:
            assign_f, f_expr = arg2
        else:
            raise Exception('Illegal parameter')
        from mathsolver.functions.budengshi.ineq_constants import INEQ_SEARCH_KEY, EQS_SEARCH_KEY
        eqs = self.search(EQS_SEARCH_KEY)
        ineqs = arg1.sympify()
        from mathsolver.functions.budengshi.qiuzuizhi import FindOptimizeValue
        text = arg3
        if text.find('最大值') >= 0:
            max_value = FindOptimizeValue(known={EQS_SEARCH_KEY: eqs, INEQ_SEARCH_KEY: ineqs}).solver(BasePoly(f_expr),
                                                                                                      '最大值')
            self.steps.extend(max_value.steps)
            self.output.extend(max_value.output)
        elif text.find('最小值') >= 0:
            min_value = FindOptimizeValue(known={EQS_SEARCH_KEY: eqs, INEQ_SEARCH_KEY: ineqs}).solver(BasePoly(f_expr),
                                                                                                      '最小值')
            self.steps.extend(min_value.steps)
            self.output.extend(min_value.output)
        elif text.find('范围') >= 0:
            max_value = FindOptimizeValue(known={EQS_SEARCH_KEY: eqs, INEQ_SEARCH_KEY: ineqs}).solver(BasePoly(f_expr),
                                                                                                      '最大值')
            self.steps.extend(max_value.steps)
            min_value = FindOptimizeValue(known={EQS_SEARCH_KEY: eqs, INEQ_SEARCH_KEY: ineqs}).solver(BasePoly(f_expr),
                                                                                                      '最小值')
            self.steps.extend(min_value.steps)
            interval = Interval(min_value.output[0].sympify(), max_value.output[0].sympify())
            self.steps.append(['所以求得:' + new_latex(f_expr), '取值范围为:' + new_latex(interval)])
            if assign_f:
                self.output.append(BaseSymbolValue({assign_f: interval}))
            else:
                self.output.append(co.to_base_interval(interval))
        else:
            raise Exception('Illegal parameter')
        return self


class XianXingGuiHua(BaseFunction):
    CLS = [FirstXianXingGuiHua, LinearProgramming, LinearProgrammingDistance, XianXingGuiHhua001, OptimalSolution001,
           OptimalSolution, OptimalSolutionPoint,
           SlopeOptimalSolution, HanCanXianXingGuiHua, XianXingGuiHua008]

    def solver(self, *args):
        for arg in args:
            if isinstance(arg, BaseEq) or isinstance(arg, BasePoly):
                if isinstance(arg, BaseEq):
                    _, f_expr = arg.sympify()
                else:
                    f_expr = arg.sympify()
                x_y_coef = f_expr.coeff('x*y')
                if x_y_coef:
                    return MatheOptimizeValue(known=self.known).solver(*args)

        arg0 = args[0]
        ineqs = []
        flag = False
        if isinstance(arg0, BaseIneq):
            ineqs.append(arg0.sympify())
        elif isinstance(arg0, BaseIneqs):
            ineqs.extend(arg0.sympify())
        for ineq in ineqs:
            _f = ineq[0] - ineq[2]
            if _f.coeff('x*y'):
                flag = True
            d_list = degree_list(_f)
            d_list = list(filter(lambda _: _ >= 2, d_list))
            if d_list:
                flag = True
        if flag:
            return MatheOptimizeValue(known=self.known).solver(*args)
        r = None
        for cl in XianXingGuiHua.CLS:
            try:
                r = cl(known=self.known).solver(*args)
                break
            except Exception:
                pass
        if not r:
            raise Exception('Can not solve the optimum value')
        return r


# 用来测试的
class XianXingGuiHuaTest(BaseFunction):
    def solver(self, *args):
        self.output.append(BaseNumber(1))
        return self


if __name__ == '__main__':
    pass
